﻿using UnityEngine;
using System.Collections;

namespace RootMotion.Dynamics
{

    // Handles switching BehaviourPuppet states.
    public partial class BehaviourPuppet : BehaviourBase
    {

        public Vector3 getUpPosition { get; set; }
        private bool dropPropFlag;

        // Force the puppet to another state
        public void SetState(State newState)
        {
            // If already in this state, do nothing
            if (state == newState) return;

            switch (newState)
            {

                // Switching to the PUPPET state
                case State.Puppet:
                    puppetMaster.SampleTargetMappedState();
                    unpinnedTimer = 0f;
                    getUpTimer = 0f;
                    hasCollidedSinceGetUp = false;
                    //getupAnimationBlendWeight = 0f;
                    //getupAnimationBlendWeightV = 0f;

                    // If switching from the unpinned state...
                    if (state == State.Unpinned)
                    {
                        // Repin the puppet
                        foreach (Muscle m in puppetMaster.muscles)
                        {
                            if (!m.state.isDisconnected)
                            {
                                m.state.pinWeightMlp = 1f;
                                m.state.muscleWeightMlp = 1f;
                                m.state.muscleDamperAdd = 0f;
                                m.positionOffset = Vector3.zero;

                                // Change physyc materials
                                SetColliders(m, false);
                            }
                        }
                    }

                    // Call events
                    state = State.Puppet;

                    if (eventsEnabled)
                    {
                        onRegainBalance.Trigger(puppetMaster);
                        if (onRegainBalance.switchBehaviour) return;
                    }
                    
                    break;

                // Switching to the UNPINNED state
                case State.Unpinned:
                    unpinnedTimer = 0f;
                    getUpTimer = 0f;
                    getupAnimationBlendWeight = 0f;

                    foreach (Muscle m in puppetMaster.muscles)
                    {
                        if (hasBoosted) m.state.immunity = 0f;
                        
                        if (maxRigidbodyVelocity != Mathf.Infinity)
                        {
                            m.rigidbody.linearVelocity = Vector3.ClampMagnitude(m.rigidbody.linearVelocity, maxRigidbodyVelocity);
                            m.mappedVelocity = Vector3.ClampMagnitude(m.mappedVelocity, maxRigidbodyVelocity);
                        }
                        
                        // Change physic materials
                        SetColliders(m, true);
                    }

                    // Drop all the props
                    if (dropProps)
                    {
                        dropPropFlag = true;
                    }

                    foreach (Muscle m in puppetMaster.muscles)
                    {
                        if (!m.state.isDisconnected)
                        {
                            m.state.muscleWeightMlp = puppetMaster.isAlive ? unpinnedMuscleWeightMlp : puppetMaster.stateSettings.deadMuscleWeight;
                        }
                    }

                    // Trigger events
                    onLoseBalance.Trigger(puppetMaster, puppetMaster.isAlive);
                    
                    // Trigger some more events
                    if (state == State.Puppet)
                    {
                        onLoseBalanceFromPuppet.Trigger(puppetMaster, puppetMaster.isAlive);
                    }
                    else
                    {
                        onLoseBalanceFromGetUp.Trigger(puppetMaster, puppetMaster.isAlive);
                    }

                    // Switch behaviours
                    if (onLoseBalance.switchBehaviour)
                    {
                        state = State.Unpinned;
                        return;
                    }
                    if (state == State.Puppet)
                    {
                        if (onLoseBalanceFromPuppet.switchBehaviour)
                        {
                            state = State.Unpinned;
                            return;
                        }
                    } else
                    {
                        if (onLoseBalanceFromGetUp.switchBehaviour)
                        {
                            state = State.Unpinned;
                            return;
                        }
                    }

                    // Unpin the muscles. This is done after the events in case behaviours are switched and the next behaviour might need the weights as they were
                    foreach (Muscle m in puppetMaster.muscles)
                    {
                        m.state.pinWeightMlp = 0f;
                    }

                    break;

                // Switching to the GETUP state
                case State.GetUp:
                    unpinnedTimer = 0f;
                    getUpTimer = 0f;
                    hasCollidedSinceGetUp = false;

                    // Is the ragdoll facing up or down?
                    bool isProne = IsProne();
                    state = State.GetUp;

                    // Trigger events
                    if (isProne)
                    {
                        onGetUpProne.Trigger(puppetMaster);
                        if (onGetUpProne.switchBehaviour) return;
                    }
                    else
                    {
                        onGetUpSupine.Trigger(puppetMaster);
                        if (onGetUpSupine.switchBehaviour) return;
                    }

                    // Unpin the puppet just in case
                    foreach (Muscle m in puppetMaster.muscles)
                    {
                        if (!m.state.isDisconnected)
                        {
                            /*
                            m.state.muscleWeightMlp = 0f;
                            m.state.pinWeightMlp = 0f;
                            m.state.muscleDamperAdd = 0f;
                            */

                            // Change physic materials
                            SetColliders(m, false);
                        }
                    }

                    // Set the target's rotation
                    if (isQuadruped)
                    {
                        Vector3 spineDirection = puppetMaster.muscles[0].rigidbody.rotation * hipsForward;
                        Vector3 normal = puppetMaster.targetRoot.up;
                        Vector3.OrthoNormalize(ref normal, ref spineDirection);
                        RotateTarget(Quaternion.LookRotation(spineDirection, puppetMaster.targetRoot.up));
                    }
                    else
                    {
                        Vector3 spineDirection = puppetMaster.muscles[0].rigidbody.rotation * hipsUp;
                        Vector3 normal = puppetMaster.targetRoot.up;
                        Vector3.OrthoNormalize(ref normal, ref spineDirection);
                        RotateTarget(Quaternion.LookRotation((isProne ? spineDirection : -spineDirection), puppetMaster.targetRoot.up));
                    }

                    // Set the target's position
                    puppetMaster.SampleTargetMappedState();
                    Vector3 getUpOffset = isProne ? getUpOffsetProne : getUpOffsetSupine;
                    MoveTarget(puppetMaster.muscles[0].rigidbody.position + puppetMaster.targetRoot.rotation * getUpOffset);
                    GroundTarget(groundLayers);
                    getUpPosition = puppetMaster.targetRoot.position;
                    
                    getupAnimationBlendWeight = 1f;
                    getUpTargetFixed = false;

                    break;
            }

            // Apply the new puppet state
            state = newState;
        }

        /// <summary>
        /// Sets the colliders of the puppet to pinned/unpinned materials.
        /// </summary>
        public void SetColliders(bool unpinned)
        {
            foreach (Muscle m in puppetMaster.muscles)
            {
                SetColliders(m, unpinned);
            }
        }

        // Sets colliders of a muscle to puppet or unpinned mode 
        public void SetColliders(Muscle m, bool unpinned)
        {
            var props = GetProps(m.props.group);

            if (unpinned)
            {
                foreach (Collider c in m.colliders)
                {
                    c.material = props.unpinnedMaterial != null ? props.unpinnedMaterial : defaults.unpinnedMaterial;

                    // Enable colliders
                    if (props.disableColliders) c.enabled = true;
                }
            }
            else
            {
                foreach (Collider c in m.colliders)
                {
                    c.material = props.puppetMaterial != null ? props.puppetMaterial : defaults.puppetMaterial;

                    // Disable colliders
                    if (props.disableColliders)
                    {
                        Vector3 inertiaTensor = m.rigidbody.inertiaTensor;
                        c.enabled = false;
                        m.rigidbody.inertiaTensor = inertiaTensor;
                    }
                }
            }
        }

        public override void OnMuscleDisconnected(Muscle m)
        {
            base.OnMuscleDisconnected(m);

            SetColliders(m, true);
        }

        public override void OnMuscleReconnected(Muscle m)
        {
            base.OnMuscleReconnected(m);

            if (m == puppetMaster.muscles[0])
            {
                SetState(State.Puppet);
            }

            float f = state == State.Puppet ? 1f : 0f;
            m.state.pinWeightMlp = f;
            m.state.muscleWeightMlp = f;
            m.state.muscleDamperMlp = f;
            m.state.maxForceMlp = 1;
            m.state.mappingWeightMlp = 1f;

            SetColliders(m, state == State.Unpinned);
        }
    }
}
